﻿using System;
using System.Collections.Generic;
using System.Text;
using DeepHash.Document;

namespace DeepHash
{
    /// <summary>
    /// Compares two Verification documents and matches the result
    /// </summary>
    public class DocumentComparer
    {
        VerificationDocument _baseDoc = null, _targetDoc = null;

        private readonly string BASE;
        private readonly string TARGET;
        private const string BOTH =         "BOTH";
        private const string NOT_EQUAL =    "DIFF ";
        private const string EQUAL =        "EQUAL";

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="baseDoc">The base document to compare with</param>
        /// <param name="basename">A title for the base document</param>
        /// <param name="targetDoc">The target document to compare</param>
        /// <param name="targetanme">A title for the target document</param>
        public DocumentComparer(VerificationDocument baseDoc, string basename, VerificationDocument targetDoc, string targetanme)
        {
            BASE = basename;
            TARGET = targetanme;
            _baseDoc = baseDoc;
            _targetDoc = targetDoc;
        }

        /// <summary>
        /// Starts comaparing the two documents against each other.
        /// </summary>
        public void Compare()
        {
            // Create a dictionary to store the paths of the base doc
            Dictionary<string, string> baseDic = DictionaryFromDocument(_baseDoc);
            // Create a dictionary to store the paths of the target doc
            Dictionary<string, string> targetDic = DictionaryFromDocument(_targetDoc);

            // We will use this dictionary to merge the contents of each documents. So
            // this dictionary will  contain all the paths from  base doc + target doc.
            // Then we will go through the items in this dictionary and analyze if they
            // exist in both locations. If they do not exist in  both locations then we
            // do not have to compare the  hash, rather we can say this  file exists on
            // base doc or target doc but it is missing in other place. But if the item
            // exists on both places  then we  would compare the  hashes of baseDic and
            // targetDic. If there is a match then its great, if there is no match then 
            // we will mark that as being different.
            // We have taken a sorted dictionary so that we have the analysis result in
            // sorted manner.
            SortedDictionary<string, string> dicMergedStatus = new SortedDictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
            // 1. Fill up the base paths 
            foreach (string key in baseDic.Keys)
            {
                dicMergedStatus.Add(key, BASE);
            }
            // Fill up the target path
            foreach (string key in targetDic.Keys)
            {
                if (dicMergedStatus.ContainsKey(key))
                {
                    dicMergedStatus[key] = BOTH;
                }
                else
                {
                    dicMergedStatus.Add(key, TARGET);
                }
            }

            SortedDictionary<string, string> clone = new SortedDictionary<string, string>(dicMergedStatus);
            foreach (string key in clone.Keys)
            {
                string status = dicMergedStatus[key];
                if (status.Equals(BOTH))
                {
                    if (baseDic[key].Equals(targetDic[key]))
                    {
                        dicMergedStatus[key] = EQUAL;
                    }
                    else
                    {
                        dicMergedStatus[key] = NOT_EQUAL;
                    }
                }
                bool showThis = false;
                if (!dicMergedStatus[key].Equals(EQUAL))
                {
                    showThis = true;
                }
                else
                {
                    if (ShowEqual)
                    {
                        showThis = true;
                    }
                }

                if (showThis)
                {
                    Console.WriteLine(GetFormatString(), dicMergedStatus[key], key);
                }
            }
        }

        private string GetFormatString()
        {
            return this.OutputFormat.Replace("[status]", "{0}").Replace("[path]", "{1}");
        }

        private bool _showEqual = false;
        public bool ShowEqual
        {
            get { return _showEqual; }
            set { _showEqual = false; }
        }

        private string _outputFormat = "[status] [path]";
        public string OutputFormat
        {
            get { return _outputFormat; }
            set { _outputFormat = value; }
        }

        private Dictionary<string, string> DictionaryFromDocument(VerificationDocument doc)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
            FillDictionary(string.Empty, doc, dic);
            return dic;
        }

        private void FillDictionary(string parentPath, FolderVerificationRecord folder, Dictionary<string, string> dic)
        {
            foreach (FileVerificationRecord file in folder.Files)
            {
                string key = JoinPath(parentPath, file.Name);
                if (!dic.ContainsKey(key))
                {
                    dic.Add(key, file.HashString);
                }
            }

            foreach (FolderVerificationRecord childFolder in folder.Folders)
            {
                FillDictionary(JoinPath(parentPath, childFolder.Name), childFolder, dic);
            }
        }

        private string JoinPath(string parent, string child)
        {
            if (parent.EndsWith("\\") || child.StartsWith("\\"))
            {
                return string.Format("{0}{1}", parent, child);
            }
            else
            {
                return string.Format("{0}\\{1}", parent, child);
            }
        }
    }
}
